use std::collections::HashMap;
use std::thread;
use std::time::Duration;

struct Cacher<T>
where T: Fn(u32) -> (u32, u32)
{
    calculation: T,
    value: HashMap<u32, u32>,
}

impl <T> Cacher<T>
where T: Fn(u32) -> (u32, u32)
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher{
            calculation,
            value: HashMap::new(),
        }
    }

    fn value(&mut self, arg: u32) -> u32 {

        match self.value.get(&arg) {
            None => {
                let v = (self.calculation)(arg);
                self.value.insert(v.0, v.1);
                v.1
            }
            Some(v) => *v
        }
    }
}

pub fn generate_workout(intensity: u32, random_number: u32) {

    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        (num, num)
    });

    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
            expensive_result.value(intensity)
        );
        println!(
            "Next, do {} situps!",
            expensive_result.value(intensity)
        );
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.value(intensity)
            );
        }
    }
}

pub fn test() {
    let mut expensive_result = Cacher::new(|num| (num, num));
    println!("cacher first {}", expensive_result.value(1));
    println!("cacher second {}", expensive_result.value(12));
}

pub fn equal_to() {
    let x = 1;
    let equal_to_x = move |z| z == x;

    let y = 1;
    assert!(equal_to_x(y));

}